package cn.lingyangwl.agile.msg.push.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.lingyangwl.agile.infra.*;
import cn.lingyangwl.agile.infra.module.group.model.rqrs.*;
import cn.lingyangwl.agile.model.constants.CommonCons.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.enums.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.agile.msg.push.*;
import cn.lingyangwl.agile.msg.push.assembly.*;
import cn.lingyangwl.agile.msg.push.core.*;
import cn.lingyangwl.agile.msg.push.manager.*;
import cn.lingyangwl.agile.msg.push.mapper.*;
import cn.lingyangwl.agile.msg.push.model.entity.*;
import cn.lingyangwl.agile.msg.push.model.rqrs.notice.*;
import cn.lingyangwl.agile.msg.push.model.rqrs.notice.MsgNoticeResp.*;
import cn.lingyangwl.agile.msg.push.service.*;
import cn.lingyangwl.agile.tenant.core.*;
import cn.lingyangwl.agile.tenant.core.utils.*;
import cn.lingyangwl.framework.data.mate.annotations.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 消息模板
 *
 * @author shenguangyang
 */
@Slf4j
@Service
public class MsgNoticeServiceImpl extends ServiceImpl<MsgNoticeMapper, MsgNotice>
    implements IService<MsgNotice>, MsgNoticeService {

    @Resource
    private MsgNoticeAssembly msgNoticeAssembly;
    @Resource
    private InfraApi infraApi;
    @Resource
    private MsgHistoryManager historyManager;
    @Resource
    private TenantDataIsolateService dataIsolateService;
    @Resource
    private NoticeChannelService noticeChannelService;
    @Resource
    private MsgNoticeMapper msgNoticeMapper;

    @Override
    @FieldBindHandle
    @Tenant(level = DataIsolateLevel.MGT)
    public List<MsgNoticeResp> listNotice(MsgNoticeQueryReq req) {
        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        LambdaQueryWrapper<MsgNotice> lqw = MsgNotice.lqw()
            .like(StringUtils.isNotEmpty(req.getName()), MsgNotice::getName, req.getName())
            .eq(StringUtils.isNotEmpty(req.getStatus()), MsgNotice::getStatus, req.getStatus())
            .eq(StringUtils.isNotEmpty(req.getReceiverType()), MsgNotice::getReceiverType, req.getReceiverType())
            .eq(Objects.nonNull(req.getGroupId()), MsgNotice::getGroupId, req.getGroupId())
            .ge(Objects.nonNull(beginTime), MsgNotice::getCreatedAt, beginTime)
            .le(Objects.nonNull(endTime), MsgNotice::getCreatedAt, endTime)
            .orderByDesc(MsgNotice::getCreatedAt);
        List<MsgNotice> list = this.list(lqw);
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        dataIsolateService.processQueryResult(list);

        List<Long> noticeIds = list.stream().map(MsgNotice::getId).collect(Collectors.toList());

        List<NoticeChannel> channelList = noticeChannelService.listChannelByNoticeIds(noticeIds);
        Map<String, NoticeChannel> channelMap = channelList.stream()
            .collect(Collectors.toMap(e -> e.getNoticeId() + ":" + e.getChannelType(), Function.identity(), (k1, k2) -> k2));

        Stream<MsgNotice> stream = list.stream();
        stream = stream.filter(e -> StringUtils.isEmpty(req.getChannelType()) || e.getChannelTypes().contains(req.getChannelType()));

        return stream.map(e -> {
            MsgNoticeResp resp = msgNoticeAssembly.toMsgNoticeResp(e);
            Set<String> channelTypes = resp.getChannelTypes();
            List<Channel> channelRespList = channelTypes.stream().map(channelType -> {
                String key = e.getId() + ":" + channelType;
                Channel channel = new Channel();
                channel.setCode(channelType);
                channel.setStatus(Optional.ofNullable(channelMap.get(key)).map(NoticeChannel::getStatus).orElse(UseStats.DISABLE));
                channel.setId(Optional.ofNullable(channelMap.get(key)).map(NoticeChannel::getId).orElse(null));
                return channel;
            }).collect(Collectors.toList());
            resp.setChannelList(channelRespList);
            return resp;
        }).collect(Collectors.toList());
    }

    @Override
    @Tenant(level = DataIsolateLevel.MGT)
    public MsgNotice getNotice(Long id) {
        MsgNotice entity = this.baseMapper.getNoticeById(id);
        if (Objects.isNull(entity)) {
            return null;
        }
        dataIsolateService.processQueryResult(entity);
        return entity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveNotice(MsgNoticeSaveReq req) {
        checkSaveOrUpdate(req);

        MsgNotice entity = msgNoticeAssembly.toMsgNotice(req);
        Map<String, MsgNotice.MsgField> fieldMap = JSON.parseArray(entity.getMsgFields(), MsgNotice.MsgField.class).stream()
            .collect(Collectors.toMap(MsgNotice.MsgField::getKey, Function.identity(), (k1, k2) -> k2));
        entity.setMsgFields(JSON.toJSONString(fieldMap.values(), JSONWriter.Feature.WriteMapNullValue));
        TenantContext tenantContext = TenantContext.get();

        boolean mgtSceneTenant = tenantContext.isMgtSceneTenant();
        if (mgtSceneTenant) {
            this.save(entity);

            // 初始化关联的渠道
            for (String channelType : req.getChannelTypes()) {
                NoticeChannelSaveReq saveReq = new NoticeChannelSaveReq();
                saveReq.setNoticeId(entity.getId());
                saveReq.setChannelType(channelType);
                saveReq.setSettings(new MsgSettings());
                saveReq.setStatus(UseStats.DISABLE);
                this.noticeChannelService.saveNoticeChannel(saveReq);
            }

        }

        dataIsolateService.saveData(entity);
    	return true;
    }

    public void checkSaveOrUpdate(MsgNoticeSaveReq req) {
        LambdaQueryWrapper<MsgNotice> lqw = MsgNotice.lqw()
            .ne(Objects.nonNull(req.getId()), MsgNotice::getId, req.getId())
            .eq(MsgNotice::getCode, req.getCode());
        if (this.count(lqw) > 0) {
            throw new BizException("消息编码【{}】已存在", req.getCode());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateNotice(MsgNoticeSaveReq req) {
        checkSaveOrUpdate(req);

        MsgNotice oldNotice = TenantUtils.execute(DataIsolateLevel.MGT, () -> this.getById(req.getId()));
        if (Objects.isNull(oldNotice)) {
            throw new BizException("消息通知不存在");
        }
        MsgNotice entity = msgNoticeAssembly.toMsgNotice(req);

        TenantContext tenantContext = TenantContext.get();
        boolean mgtSceneTenant = tenantContext.isMgtSceneTenant();

        Map<String, MsgNotice.MsgField> fieldMap = JSON.parseArray(entity.getMsgFields(), MsgNotice.MsgField.class).stream()
            .collect(Collectors.toMap(MsgNotice.MsgField::getKey, Function.identity(), (k1, k2) -> k2));

        entity.setMsgFields(JSON.toJSONString(fieldMap.values(), JSONWriter.Feature.WriteMapNullValue));

        if (mgtSceneTenant) {
            this.updateById(entity);

            // 初始化新增的渠道，并删除已被移除的渠道
            Set<String> oldChannelTypes = new HashSet<>(JSON.parseArray(oldNotice.getChannelTypes(), String.class));
            Set<String> deleteChannelTypes = new HashSet<>(oldChannelTypes);
            deleteChannelTypes.removeAll(req.getChannelTypes());

            // 删除渠道
            noticeChannelService.deleteChannelByNoticeId(entity.getId(), new ArrayList<>(deleteChannelTypes));

            // 初始化新增的渠道
            Set<String> newChannelTypes = new HashSet<>(req.getChannelTypes());
            newChannelTypes.removeAll(oldChannelTypes);

            for (String channelType : newChannelTypes) {
                NoticeChannelSaveReq saveReq = new NoticeChannelSaveReq();
                saveReq.setNoticeId(entity.getId());
                saveReq.setChannelType(channelType);
                saveReq.setSettings(new MsgSettings());
                saveReq.setStatus(UseStats.DISABLE);
                this.noticeChannelService.saveNoticeChannel(saveReq);
            }
        }
        dataIsolateService.updateData(entity);
    	return true;
    }


    @Override
    public void updateStatus(Long id, Boolean status) {
        TenantContext tenantContext = TenantContext.get();
        boolean mgtSceneTenant = tenantContext.isMgtSceneTenant();
        if (mgtSceneTenant) {
            this.update(MsgNotice.luw().eq(MsgNotice::getId, id).set(MsgNotice::getStatus, status));
        }
        MsgNotice entity = TenantUtils.execute(DataIsolateLevel.MGT, () -> this.getById(id));
        entity.setStatus(status);
        dataIsolateService.updateData(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteNotice(Long id) {
        TenantContext tenantContext = TenantContext.get();
        boolean mgtSceneTenant = tenantContext.isMgtSceneTenant();

        if (mgtSceneTenant) {
            noticeChannelService.deleteChannelByNoticeIds(Collections.singletonList(id));
            dataIsolateService.deleteData(TenantDataType.MSG_NOTICE, id);
            return this.removeById(id);
        }
        return true;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDeleteNotice(List<Long> ids) {
        TenantContext tenantContext = TenantContext.get();
        boolean mgtSceneTenant = tenantContext.isMgtSceneTenant();

        if (mgtSceneTenant) {
            noticeChannelService.deleteChannelByNoticeIds(ids);
            dataIsolateService.deleteData(TenantDataType.MSG_NOTICE, ids);
            return this.removeByIds(ids);
        }
        return true;
    }

    @Override
    @Tenant(level = DataIsolateLevel.MGT)
    public boolean batchDeleteGroup(List<Long> ids) {
        DataGroupQuery groupReq = new DataGroupQuery();
        groupReq.setBizType(DataGroupBizTypeEnum.MSG_PUSH_TASK.getType()).setIds(ids);
        List<DataGroupDto> groupList = infraApi.listDataGroup(groupReq);
        List<Long> groupIds = groupList.stream().map(DataGroupDto::getId).distinct().collect(Collectors.toList());
        if (this.count(MsgNotice.lqw().in(MsgNotice::getGroupId, groupIds)) > 0) {
            throw new BizException("所删除的功能组下有未删除的任务, 请全部删除后再删除组");
        }

        DataGroupDeleteReq deleteReq = new DataGroupDeleteReq();
        deleteReq.setBizType(DataGroupBizTypeEnum.MSG_PUSH_TASK.getType()).setIds(ids);
        infraApi.batchDeleteDataGroup(deleteReq);
        return true;
    }

    @Override
    @Tenant(level = DataIsolateLevel.MGT)
    public List<MsgNotice> listNoticeByIds(List<Long> noticeIds) {

        return msgNoticeMapper.listNoticeByIds(noticeIds);
    }

    @Override
    @Tenant(level = DataIsolateLevel.MGT)
    public MsgNotice getNoticeByCode(String noticeCode) {
        MsgNotice entity = this.getOne(MsgNotice.lqw().eq(MsgNotice::getCode, noticeCode));
        if (Objects.isNull(entity)) {
            throw new BizException("消息通知编码 [{}] 不存在", noticeCode);
        }
        dataIsolateService.processQueryResult(entity);
        return entity;
    }

    @Tenant(level = DataIsolateLevel.MGT)
    public List<MsgNotice> listNoticeByCodes(List<String> codes) {
        if (CollUtil.isEmpty(codes)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<MsgNotice> lqw = MsgNotice.lqw().in(MsgNotice::getCode, codes);
        List<MsgNotice> list = TenantUtils.execute(DataIsolateLevel.MGT, () -> this.list(lqw))
            .stream().filter(MsgNotice::getStatus).collect(Collectors.toList());
        dataIsolateService.processQueryResult(list);
        return list;
    }
}
